import tornado
import struct
from tornado.tcpserver import *
from tornado.ioloop import IOLoop
from tornado.iostream import IOStream
import tornado.gen
from collections import namedtuple
from functools import partial
import time
from source_client import SourceStatus

# коэффициэнт, на который надо умножить разницу между значениями time.clock() для того, чтобы получить величину в миллисекундах
# на моей системе это почему-то 1000000. при желании можно использовать time.time() или еще что-нибудь придумать 
CLOCK_CF = 1000000

Header = namedtuple('Header',['msg_num', 'ascii_id', 'status', 'num_fields']) 
Data = namedtuple('Data', ['num_fields', 'fields'])

class ListenersTcpServer(TCPServer):

   def __init__(self, sources_server):
      super(ListenersTcpServer, self).__init__()
      self.listeners = []
      self.sources_server = sources_server
      
   def handle_stream(self, stream, address):     
       
      print('Listener connected:%s' % str(address))
      l_conn =ListenerConnection(stream, address)
      stream.set_close_callback(partial(self.listeners.remove, l_conn))
      
      l_msg = ''
      for each_source in self.sources_server.sources:
         if hasattr(each_source, 'last_message_header'):
            l_msg = ''.join([l_msg, '[',each_source.last_message_header.ascii_id, '] ', str(each_source.last_message_header.msg_num), \
                          ' | ', SourceStatus.get_status_string(each_source.last_message_header.status),' | ', str(int(time.clock()*CLOCK_CF-each_source.last_message_time)), '\r\n'])
      
      self.listeners.append(l_conn)
      stream.write(l_msg)


class SourcesTcpServer(TCPServer):

   def __init__(self):
      super(SourcesTcpServer, self).__init__()
      self.listeners_server = ListenersTcpServer(self)
      self.listeners_server.listen(8889)
      self.sources = []
      
   def handle_stream(self, stream, address):
      
      print('Source connected:%s' % str(address))
      s_conn = SourceConnection(stream, address, self.listeners_server)
      stream.set_close_callback(partial(self.sources.remove, s_conn))
      self.sources.append(s_conn)
   

class SourceConnection(object):
   
   def __init__(self, stream, address, listeners_server):
      
      self.stream = stream
      self.address = address
      self.listener_server = listeners_server
      self.start()
      
      

   @tornado.gen.engine
   def start(self):
      self.running = True
      while self.running:
         
         header_bytes = yield tornado.gen.Task(self.stream.read_bytes, 13)
      
         self.header_bytes = bytearray(header_bytes)
         self.last_message_header = self.extract_header(header_bytes)
      
         if self.last_message_header:
         
            data_bytes = yield tornado.gen.Task(self.stream.read_bytes, self.last_message_header.num_fields*12+1)
            fields = {}
            self.data_bytes = bytearray(data_bytes)          
         
            for i in xrange(0, self.last_message_header.num_fields):
               fields[data_bytes[i*12:i*12+8]]= struct.unpack('!i', data_bytes[i*12+8:(i+1)*12])[0]
              
              
            self.last_message_data = Data(self.last_message_header.num_fields, fields)        
            self.last_message_time = time.clock()*CLOCK_CF
            self.send_response()
   
   
      
   def is_message_valid(self, message_bytes):
      return message_bytes[-1] == reduce(lambda x,y: x^y, message_bytes[:-1])
       
   def extract_header(self, header_bytes):
      if ord(header_bytes[0]) != 0x01:
         return None
      
      msg_num = struct.unpack('!H', header_bytes[1:3])[0]
      ascii_id = header_bytes[3:11]
      status = struct.unpack('!B', header_bytes[11])[0]
      num_fields = struct.unpack('!B', header_bytes[12])[0]
      return Header(msg_num, ascii_id, status, num_fields)
   
   def send_response(self):
      print(self.last_message_header)
      print(self.last_message_data)
      if self.is_message_valid(self.header_bytes+self.data_bytes):
         print('Message OK')
         self.send_ok_response()
         self.notify_listeners()
      else:
         print('Invalid message!')
         self.send_error_response()
      
   def send_ok_response(self):
      message =''.join([ '\x11', struct.pack('!H', self.last_message_header.msg_num)])
      message += chr(reduce(lambda x,y: x^y, bytearray(message)))
      self.stream.write(message)
   
   def send_error_response(self):
      message = '\x12\x00\x00'
      message += chr(reduce(lambda x,y: x^y, bytearray(message)))
      self.stream.write(message)
      
   def notify_listeners(self):
      l_msg = ''
      for f_name in self.last_message_data.fields:
         l_msg=''.join([l_msg, '[', self.last_message_header.ascii_id, '] ', f_name,' | ', str(self.last_message_data.fields[f_name]), '\r\n'])
      
      
      for each_listener in self.listener_server.listeners:
         each_listener.stream.write(l_msg)

class ListenerConnection(object):
   
   def __init__(self, stream, address):
      
      self.stream = stream
      self.address = address
   
         
   
if __name__ == '__main__':

   sources_server  = SourcesTcpServer()  
   sources_server.listen(8888)
   
   
   IOLoop.instance().start()
    
   print('Application started')